package com.mapbased.wfw;

import java.io.*;
import java.net.*;
import java.util.*;

import com.mapbased.wfw.bind.*;
import com.mapbased.wfw.components.*;
import org.dom4j.*;
import org.dom4j.io.*;

/**0
 *
 * <p>Title: </p>
 *
 * <p>Description: </p>
 *
 * <p>Copyright: Copyright (c) 2004</p>
 *
 * <p>Company: mapbased.com</p>
 *
 * @author chy_hs(Baily)
 * @version 1.0
 */


public class PageLoader
    //  implements Runnable
{

    public static class LoadContext
    {
        LoadContext(String loadingPagepath,PageLoader loader,Page page)
        {
            this.pagepath=loadingPagepath;
            this.loader=loader;
            this.page=page;
        }

        public LoadContext(LoadContext old)
        {
            this.pagepath=old.pagepath;
            this.loader=old.loader;
            this.page=old.page;
        }

        public final String pagepath;
        public final PageLoader loader;
        /**
         * while loading ,the refrence is right,but some of it's content may not init;
         */
        public final Page page;

        public final List segments=new java.util.ArrayList();
        public final StringBufferBag last=new StringBufferBag();
        public final Object[] getSegments()
        {
            this.finish();
            Object[] r=new Object[this.segments.size()];

            return this.segments.toArray(r);

        }

        public void finish()
        {
            if(last.buffer.length() > 0)
            {
                this.segments.add(last.getString());
            }

        }

    }

    /**
     *
     * <p>Title: Dynamic Class loader</p>
     *
     * <p>Description: Only load one class and related</p>
     *
     * <p>Copyright: Copyright (c) 2004</p>
     *
     * <p>Company: mapbased.com</p>
     *
     * @author chy_hs(Baily)
     * @version 1.0
     */

    class PClassLoader
        extends ClassLoader
    {
        private String loadingClass;

        /**
         * Finds the specified class.
         *
         * @param name The name of the class
         * @return The resulting <tt>Class</tt> object
         * @throws ClassNotFoundException If the class could not be found
         * @todo Implement this java.lang.ClassLoader method
         */
        public Class findClass(String name)
            throws ClassNotFoundException
        {
            /**
             * when first call,assign the class name,if reenter with the same
             * class,throws exception,because every classloader only use once,
             * didn't clear the lodingClass flag here
             */
            if(this.loadingClass == null)
            {
                this.loadingClass=name;
            }
            else if(this.loadingClass == (name))
            {
                throw new ClassNotFoundException(name);

            }
            String n=new StringBuffer("/WEB-INF/classes/")
                .append(name.replace('.','/'))
                .append(".class").toString();
            try
            {
                URL u=PageLoader.this.getResource(n);
                if(u == null)
                {
                    return Thread.currentThread().getContextClassLoader().
                        loadClass(name);
                }
                java.io.ByteArrayOutputStream bos=new java.io.
                    ByteArrayOutputStream();
                java.io.InputStream is=u.openStream();
                for(int i=is.read();i >= 0;i=is.read())
                {
                    bos.write(i);
                }
                is.close();
                byte[] bs=bos.toByteArray();
                return this.defineClass(name,bs,0,bs.length);
            }
            catch(Exception ex)
            {
                throw new ClassNotFoundException(name,ex);
            }

        }

        /**
         * Loads the class with the specified name.
         *
         * @param name The name of the class
         * @return The resulting <tt>Class</tt> object
         * @throws ClassNotFoundException If the class was not found
         * @todo Implement this java.lang.ClassLoader method
         */
        public Class loadClass(String name)
            throws ClassNotFoundException
        {
            if(name.indexOf(this.loadingClass) >= 0) //for inner calss
            {
                return this.findClass(name);
            }
            try
            {

                return Thread.currentThread().getContextClassLoader().
                    loadClass(name);
            }
            catch(ClassNotFoundException ex)
            {
                //if 2 classes in "/WEB-INF/classes/" one references another,
                //the classed be refreenced will be loaded from here
                return this.findClass(name);
            }

        }

        /**
         * used?may be unused
         * @param name String
         * @return URL
         */

        public URL getResource(String name)
        {
            URL u=null;
            try
            {
                u=PageLoader.this.getResource("/WEB-INF/classes/" + name);
            }
            catch(MalformedURLException ex)
            {
                throw new java.lang.RuntimeException(ex);
            }
            //  System.out.println(u);

            if(u == null)
            {
                return super.getResource(name);
            }
            return u;

        }

    };
    //  private PClassLoader classLoader=new PClassLoader();
    public static final String JWCID="jwcid";

    private Map pages=new java.util.concurrent.ConcurrentHashMap(
        );
    private static Map componentClasses=new java.util.HashMap();
//    private Page errorPage;

    //  private String errorPagePath;
    /**
     * the page is current being loaded
     */
    //private String loadingPage;
    private Object loadLockor=new Object();
    private GlobalRef global;
    private boolean developeMode;
    private String defaulPagePath="defaulPagePath";
    public static final String GLOBAL_PAGE_PATH="GlobalPagePath";
   // private Thread idlePageCleaner=new  Thread(this,"idle-page-cleaner");
    void init()
    {

        componentClasses.put(Condition.ComponentName,new Condition().getClass());
        componentClasses.put("include",new Include().getClass());
        componentClasses.put("body",new Body().getClass());
        componentClasses.put("border",Border.class);
        componentClasses.put("use",Use.class);

        componentClasses.put(Repeat.ComponentName,
                             new Repeat().getClass()
            );
        Config c=this.getGlobal().getWfwConfig();
        String s=c.getValue("develope-mode",
            "false");
        this.setDevelopeMode("true".equalsIgnoreCase(s.trim()));
      //  this.getGlobal().servletContext.getInitParameter("defaultExtName");

    //  this.defaultExtName=c.getValue("defaultExtName",".html");



        this.getGlobal().timer.schedule(new java.util.TimerTask(){
        public void run()
        {
            System.out.println("clean");
            PageLoader.this.removeIdle();
        }
        },c.getInterval("cleaner-delay","2h"),c.getInterval("cleaner-period","1h"));


    }

    public Map getPages()
    {
        return this.pages;
    }

    public Page getPage(RenderContext context)
    {
        String path=context.getRequest().getServletPath();
        //  String locale;
        try
        {
            Page p= this.innerGetPage(path,context.getLocale());
          //  p.lastVisitTime=System.currentTimeMillis();
            return p;
        }
        catch(Exception ex)
        {
            context.getRequestMap().put(Constants.Error_Page_Throwable,ex);
            return null;
        }

    }

    public Page getPage(String s,String locale)
    {
        try
        {
              Page p=this.innerGetPage(s,locale);
             // p.lastVisitTime=System.currentTimeMillis();
              return p;
        }
        catch(Exception ex)
        {
            throw new java.lang.RuntimeException(ex);
            //context.getRequestMap().put(Constants.Error_Page_Throwable,ex);
            //  return null;
        }

    }

    private Page innerGetPage(String path,String locale)
        throws Exception
    {
        /**
         * 忽略第一个以！开头的目录
         * 为什么这么做？
         * 希望将参数映射成URL目录，Cookie用到
         * 此处只是忽略掉参数，如果希望得到参数，可以从RenderContext
         * 通过 String path=context.getRequest().getServletPath();
         * 获取
         *
         */
        while(path.charAt(1) == '!')
        {
            int i=path.indexOf('/',1);

            path=(i > 0) ? path.substring(i) : "/" + path.substring(2);

        }

        if(this.developeMode)
        {
            //this.pages.clear();

            return this.loadPage(this.localeResStr(path,locale));
        }

        if(locale != null)
        {
            int pindex=path.lastIndexOf('.');
            String tail=path.substring(pindex);
            StringBuffer sb=new StringBuffer(
                path.substring(0,pindex));
            sb.append('_').append(locale).append(tail);
            String full=sb.toString();
            Object o=this.pages.get(full);
            if(o != null)
            {
                return(Page)o;
            }
            String s=this.localeResStr(path,locale);
            Page p=this.loadPage(s);
            if(s.length() != full.length() && p != null)
            {
                //   synchronized(this.loadLockor)
                {
                    this.pages.put(full,p);
                }
            }
            return p;

            /* int lindex=locale.indexOf('_');
             if(lindex > 0)
             {

                 sb.setLength(pindex);
             sb.append('_').append(locale.substring(0,lindex)).append(tail);
                 o=this.pages.get(sb.toString());
                 if(o != null)
                 {
                     return(Page)o;
                 }

             }*/

        }

        Page p=(Page)this.pages.get(path);

        if(p == null)
        {
            return this.loadPage(path);
        }
        return p;
        /*     if(path == null || path.trim().equalsIgnoreCase(""))
         {
             return null;
         }
         Object o=this.pages.get(path);
         if(o != null)
         {
             return(Page)o;
         }

             File pf=new File(path);
         if(pf.isFile())
         {
             return this.loadPage(pf);
         }
         return null;*/

    }

    static private String getLocaleFromPath(String path)
    {
        String[] dirs=path.split("/");
        String file=dirs[dirs.length - 1];
        int i=file.indexOf('_');

        if(i > 0)
        {
            return file.substring(i + 1,file.indexOf('.'));
        }
        return null;
    }

    /**
     * get classname from "/com/mapbased/wfw/SomeClass_zh_CN.html"
     * format
     * @param pagePath String
     * @return String
     */
    private static String getClassNameFromPagePath(String pagePath)
    {

        int l1=pagePath.lastIndexOf('.');
        int l2=pagePath.lastIndexOf('/',l1);

        int l_=pagePath.indexOf('_',l2);
        if(l_ < 0)
        {
            l_=l1;
        }

        return pagePath.substring(1,l_).replace('/','.');

    }

    public static final String DIR_BORDER= "DirBorder.shtml";
    //private String defaultExtName;


    private Page loadPage(String pagepath)
        throws Exception
    {
        String borderPage= pagepath.substring(0,pagepath.lastIndexOf('/')+1)+DIR_BORDER ;
       boolean hasBorder=
           pagepath.endsWith( ".shtml")==false
           &&pagepath.endsWith(".xml")==false
           &&this.getResource(borderPage)!=null;

        synchronized(this.loadLockor)
        {
            /**
             * many thread may be waiting into this synchronize area
             *
             */
            if(!this.developeMode)
            {
                Object o=this.pages.get(pagepath);
                if(o != null)
                {
                    return(Page)o;
                }
            }
        //    pagepath=pagepath.intern();

            // this.loadingPage=pagepath;
            SAXReader reader=new SAXReader();
            reader.setEntityResolver(WfwEntityResolver.INSTANCE);
            //   reader.setIncludeExternalDTDDeclarations(false);

            java.net.URL pagepathurl=this.getResource(pagepath);
            if(pagepathurl == null)
            {
                return null;

            }

            Document doc=reader.read(pagepathurl);
            Element root=doc.getRootElement();
            /////////////////////////////////////////////
            org.dom4j.Attribute redir=root.attribute(Constants.AN_Redirect);
            if(redir != null)
            {
                String s=redir.getValue();
                s=String.valueOf(s).trim();
                if(s.length() > 0)
                {
                    return this.loadPage(s);
                }
            }
            ///////////////////////////////////////////////////

            Node n=doc.selectSingleNode("//*[@jwcid=\"$content$\"]");
            //  System.out.println(doc.getDocType().asXML());
            if(n != null)
            {
                root=(Element)((Element)n).elements().get(0);
            }

            if(hasBorder&&root.attribute("jwcid")==null)
            {
              Node body=  root.element("body");
              if(body==null)
              {
                 body=  root.element("BODY");
              }
              if(body!=null)
              {
                   root=(Element)((Element)body).elements().get(0);
              }
              root.addAttribute("jwcid","border");
              root.addAttribute("page",borderPage);

            }

//>>>>>>>>>>>>>>>>>build inhead string>>>>>>>>>>>>>>>>>>>>

            List l=doc.selectNodes("//*[@jwcid=\"tohead\"]");

            String heads="";

            for(int i=0;i < l.size();i++)
            {
                Element headn=(Element)l.get(i);
                headn.remove(headn.attribute("jwcid"));
                // this.parseElement(headn,tl,);
                heads+=headn.asXML();
                headn.getParent().remove(headn);

            }
//            //pase only config binding in head field,if content other kind of
//            //binding ,error will ocr
//
//            // comp.headString=s;
//////////////////////////////////////////////////////////
            Page p=new Page();

            String cn=null;

            Attribute att=doc.getRootElement().attribute(Constants.
                AN_PageHandler);
            if(att != null)
            {
                cn=att.getValue();
                doc.getRootElement().remove(att);
            }

            if(cn == null)
            {
                cn=getClassNameFromPagePath(pagepath);

            }
            if(cn.equals("null"))
            {

            }
            else if(cn.equals("cache"))
            {
                PageHandler hdler=new com.mapbased.wfw.handler.CacheHandler();
                //   hdler.init(this.servletContext);
                hdler.fetchAttributs(doc.getRootElement());
                p.setPageHandler(hdler);

            }
            else
            {
                try
                {

                    Class c=loadClass(cn);
                    //Class c=this.classLoader.loadClass(cn);
                    PageHandler hdler=(PageHandler)c.newInstance();
                    //   hdler.init(this.servletContext);
                    hdler.fetchAttributs(doc.getRootElement());
                    p.setPageHandler(hdler);

                }
                catch(Throwable ex)
                {
                    System.err.println("Error loading class:" + cn + "  :" +
                                       ex.toString());
                }
            }
            //===============================

            LoadContext context=new LoadContext(pagepath,this,p);

            List segments=context.segments;
            StringBufferBag bag=context.last;
            if(doc.getDocType() != null)
            {
                bag.buffer.append(doc.getDocType().asXML());
            }

            parseElement(root,context);
            if(bag.buffer.length() > 0)
            {
                segments.add(bag.getString());
            }

            if(!heads.equals(""))
            {

//              //List segs=new java.util.Collections.();
//              StringBufferBag ba=new StringBufferBag();
//
//              //only parse config binding
//              this.parseBinding(Util.removeSpace(heads),
//                                java.util.Collections.EMPTY_LIST,ba);

                //   heads=ba.buffer.toString();
                LoadContext nlc=new LoadContext(context);
                this.parseBinding(Util.removeSpace(heads),nlc);

                p.headSegments=nlc.getSegments();

            }

            p.setSegments(segments.toArray());
            //==============

            p.getPageHandler().setLocale(getLocaleFromPath(pagepath));

            p.getPageHandler().setPagePath(pagepath);

            p.getPageHandler().init(this.global);

            this.pages.put(pagepath,p);
            p.getPageHandler().validators=p.findValidators();
            return p;

        }

    }

    /**
     *Try to LoadClass Dynamicially,only used to load PageHandler and Component
     * @param name String
     * @return Class
     * @throws ClassNotFoundException
     */
    private Class loadClass(String name)
        throws ClassNotFoundException
    {
//        if(this.developeMode)
//        {
        return new PClassLoader().findClass(name);
//        }
//
//        return Thread.currentThread()
//            .getContextClassLoader().loadClass(name);
    }

    java.net.URL getResourceFromFS(String baseDir,String s)
        throws MalformedURLException
    {
        File f=new File(baseDir,s);
        if(f.exists())
        {
            if(this.isDevelopeMode())
            {
                new File(f.toString().concat(".need")).delete();
            }
            return f.toURL();

        }
        if(this.isDevelopeMode())
        {
            try
            {
                f.getParentFile().mkdirs();
                new File(f.toString().concat(".need")).createNewFile();
            }
            catch(IOException ex)
            {
                System.out.println(f.toString() + ":" + ex);
            }
        }
        return null;

    }

    java.net.URL getResourceFromWar(String s)
        throws MalformedURLException
    {
        java.net.URL u=this.global.servletContext.getResource(s);
        if(u == null)
        {
            //remove first "/"  get from webcontext "/com/chy/sss.html"
            //get from class path using "com/chysss.html" formate
            u=Thread.currentThread().getContextClassLoader().getResource(s.
                substring(1));
        }
        return u;

    }

    /**
     * First get html from default pagepath,if not found ,get from
     *  webcontainer ,if not found ,
     * try to get from classpath
     *
     *First default pagepath Then webcontainer,last classpath
     * @param s String
     * @return URL
     */
    public java.net.URL getResource(String s)
        throws MalformedURLException
    {
        java.net.URL u=this.getResourceFromFS(this.defaulPagePath,s);
        if(u != null)
        {
            return u;
        }
        u=this.getResourceFromFS(GLOBAL_PAGE_PATH,s);
        if(u != null)
        {

            return u;
        }
        return this.getResourceFromWar(s);

    }

    public static class StringBufferBag
    {

        public StringBuffer buffer=new StringBuffer();
        public String getString()
        {
            String s=buffer.toString();
            buffer=new StringBuffer();
            return s;

        }
    }

    /**
     * single value avaliable dependent attribute
     */
    private void parseSVADAttribute(Attribute a,List seg,LoadContext lc)
    {
        String s=a.getName();
        String eg=s.substring(0,s.length() - 1);
        final String trues=new StringBuffer(" ").append(eg).append("=\"").
            append(eg).
            append('"').toString();
        //List l=new ArrayList(1);
        LoadContext nlc=new LoadContext(lc);
        List l=nlc.segments;
        parseBinding(a.getValue(),nlc);
        final Binding b=(Binding)l.get(0);
        seg.add(new Binding()
        {
            public Object getValue(RenderContext ct)
            {
                Object v=b.getValue(ct);
                if(v == null)
                {
                    return null;
                }
                if(v instanceof Boolean)
                {
                    return((Boolean)v).booleanValue() ? trues : null;
                }
                if(v instanceof String)
                {
                    String ss=((String)v).trim();

                    if(ss.equalsIgnoreCase("false") || ss.equalsIgnoreCase("no") ||
                       ss.equalsIgnoreCase("off"))
                    {
                        return null;
                    }
                    return trues;
                }
                if(v instanceof Number)
                {
                    return((Number)v).intValue() != 0 ? trues : null;
                }
                if(v instanceof Collection)
                {
                    return((Collection)v).size() > 0 ? trues : null;
                }
                return trues;

            }
        });
    }

    public void parseNameSpace(Element ele,List segments,StringBufferBag last)
    {
        Namespace nsp=ele.getNamespace();
        if(nsp != null)
        {
            if(ele.getParent() == null ||
               !nsp.getURI().equalsIgnoreCase(ele.getParent().getNamespace().
                                              getURI()))
            {
                String s=nsp.asXML();
                if(s.length() > 10) //make sure not add empty nsp
                {
                    last.buffer.append(" ").append(s);
                }
            }
        }

    }

    public void parseAttributes(List attributes,LoadContext lc)
    {
        List segments=lc.segments;
        StringBufferBag last=lc.last;

        for(int i=0;i < attributes.size();i++)
        {
            Attribute an=(Attribute)attributes.get(i);
            String anmame=an.getName();
            if(anmame.endsWith("-"))
            {
                if(last.buffer.length() > 0)
                {
                    segments.add(last.getString());
                }
                this.parseSVADAttribute(an,segments,lc);
                continue;
            }
            //=(Attribute)n;
            last.buffer.append(" ");
            last.buffer.append(anmame);
            last.buffer.append("=\"");
            parseBinding(an.getValue(),lc);
            last.buffer.append('"');
        }

    }

    /**
     *
     * Can only be called from a component while init
     * @param ele Element
     * @param segments List
     * @param last StringBufferBag
     */
    public void parseElement(Element ele,LoadContext lc)
    {
        List segments=lc.segments;
        StringBufferBag last=lc.last;

        String a=ele.attributeValue(JWCID);
        if(a != null)
        {
            if(a.equalsIgnoreCase("$remove$"))
            {
                return;
            }
            if(last.buffer.length() > 0)
            {
                segments.add(last.getString());

            }
            segments.add(parseComponent(ele,lc));
            return;

        }
//////////////////////////////////////////////////////////////////////////
        this.resolveUrl(ele,lc);
        last.buffer.append("<").append(ele.getName());
        this.parseAttributes(ele.attributes(),lc);
        this.parseNameSpace(ele,segments,last);
        if(!ele.hasContent())
        {
            String name=ele.getName();
            if("input".equalsIgnoreCase(name)
               || "meta".equalsIgnoreCase(name)
               || "link".equalsIgnoreCase(name)
               || "img".equalsIgnoreCase(name)
               || "br".equalsIgnoreCase(name)

                )
            {

                last.buffer.append(" />");

                return;
            }

        }

        last.buffer.append(">");

//////////////////////////////////////////////////////////////////////////
        parseElementChildren(ele,lc);
        last.buffer.append("</").append(ele.getName()).append(">");

    }

    /**
     *
     * Can only be called from a component while init
     * @param ele Element
     * @param segments List
     * @param last StringBufferBag
     */
    public void parseElementChildren(Element ele,LoadContext lc)
    {
        //  List segments=lc.segments;
        StringBufferBag last=lc.last;

         Node n=null;
         boolean look=true;
        for(Iterator it=ele.nodeIterator();;)
        {
            if(look)
            {
                if(it.hasNext())
                {
                    n=(Node) it.next();

                }
                else
                {
                    break;
                }
            }
             look=true;
            switch(n.getNodeType())
            {
                case Node.ELEMENT_NODE:
                    parseElement((Element)n,lc);

                    break;

                case Node.NAMESPACE_NODE:

                case Node.ATTRIBUTE_NODE:
                    break;

                case Node.CDATA_SECTION_NODE:
                    /**
                     * @todo:handle in future
                     */
                    //  parseBinding(Util.removeSpace(n.getText()),lc);
                case Node.COMMENT_NODE:
                    parseBinding(Util.removeSpace(n.asXML()),lc);
                    break;

                case Node.PROCESSING_INSTRUCTION_NODE:
                    last.buffer.append(" ").append(n.asXML());
                    break;

                    /**
                     * here because dom4j devide some text component unexpected
                     */
                    case Node.TEXT_NODE:

                    //Text t=(Text)n;
                    StringBuilder tsb=new StringBuilder(n.getText());
                    while(it.hasNext())
                    {
                        n=(Node)it.next();
                        if(n.getNodeType()==Node.TEXT_NODE)
                        {
                            tsb.append(n.getText());
                        }
                        else
                        {
                            look=false;
                            break;
                        }
                    }


                    parseBinding(Util.removeSpace(tsb.toString()),lc);

                    //  parseBinding(n.getText(),lc);

                    break;

            }
        }

    }

    public void parseBinding(String s,LoadContext lc)
    {
        if(s == null)
        {
            return;
        }
        List segments=lc.segments;
        StringBufferBag last=lc.last;
        for(int i=0;i < s.length();i++)
        {
            char c=s.charAt(i);
            if(c == '@')
            {
                String bs=s.substring(i + 1);
                if(bs.startsWith("session{"))
                {
                    lc.finish();
                    segments.add(new SessionBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                else if(bs.startsWith("page{"))
                {
                    lc.finish();
                    segments.add(new PageBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                else if(bs.startsWith("pageconfig{") ||
                        bs.startsWith("pageConfig{"))
                {
                    /*if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    PageConfigBinding pcb=new PageConfigBinding(parseCmd(bs));
                    pcb.setPage(lc.page);
                    segments.add(pcb);
                    parseBinding(removeBindPart(bs),segments,last);
                    return;  */

                    lc.finish();
                   PageConfigBinding o=new PageConfigBinding(parseCmd(bs));

                   o.setPage(lc.page);
                   segments.add(o);
                   parseBinding(removeBindPart(bs),lc);
                   return;




                }

                else if(bs.startsWith("opage{"))
                {
                    lc.finish();
                    OPageBinding o=new OPageBinding(parseCmd(bs));

                    o.setPage(lc.page);
                    segments.add(o);
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }

                else if(bs.startsWith("static{"))
                {
                    lc.finish();
                    segments.add(new StaticBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }

                else if(bs.startsWith("component{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new ComponentBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                else if(bs.startsWith("url{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new UrlBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }

                else if(bs.startsWith("request{") || bs.startsWith("req{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new RequestBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                else if(bs.startsWith("bean{") || bs.startsWith("beans{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new BeanBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }

                else if(bs.startsWith("var{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new VariableBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                else if(bs.startsWith("cookie{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new CookieBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }
                //it's different to other bindings,
                //WHEN loading page,calculte the value
                else if(bs.startsWith("config{"))
                {
                    Config config=this.global.wfwConfig;
                    String tmps=String.valueOf(config.getValue(parseCmd(bs)));
                    if(tmps.indexOf('@') >= 0)
                    {
                        parseBinding(tmps,lc);
                    }
                    else
                    {
                        last.buffer.append(tmps);
                    }
                    parseBinding(removeBindPart(bs),lc);
                    return;

                }

                else
                {
                    last.buffer.append(c);

                }

            }
            else
            {
                last.buffer.append(c);
            }
        }

    }

    /**
     *  @session{name}
     *  @page{name}
     *  @request{name}
     *  @var{name}
     * @param s String
     * @param segments List
     * @param last StringBufferBag
     * @deprecated:cannot parse opage binding,only suite when cannot abtain a LoadContext
     */
    public void parseBinding(String s,List segments,StringBufferBag last)
    {
        if(s == null)
        {
            return;
        }
        for(int i=0;i < s.length();i++)
        {
            char c=s.charAt(i);
            if(c == '@')
            {
                String bs=s.substring(i + 1);
                if(bs.startsWith("session{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new SessionBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                else if(bs.startsWith("page{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new PageBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                else if(bs.startsWith("pageconfig{") ||
                        bs.startsWith("pageConfig{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }

//                    PageConfigBinding pcb=new PageConfigBinding(parseCmd(bs));
//                    pcb.setPage(lc.page);
//                    segments.add(pcb);

                    segments.add(new PageConfigBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }

//                else if(bs.startsWith("opage{"))
//                {
//                    if(last.buffer.length() > 0)
//                    {
//                        segments.add(last.getString());
//                    }
//                    OPageBinding o=new OPageBinding(parseCmd(bs));
//
//                //    o.setPage();
//                    segments.add(o);
//                    parseBinding(removeBindPart(bs),segments,last);
//                    return;
//
//                }

                else if(bs.startsWith("static{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new StaticBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }

                else if(bs.startsWith("component{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new ComponentBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                else if(bs.startsWith("url{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new UrlBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }

                else if(bs.startsWith("request{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new RequestBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                else if(bs.startsWith("bean{") || bs.startsWith("beans{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new BeanBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }

                else if(bs.startsWith("var{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new VariableBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                else if(bs.startsWith("cookie{"))
                {
                    if(last.buffer.length() > 0)
                    {
                        segments.add(last.getString());
                    }
                    segments.add(new CookieBinding(parseCmd(bs)));
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }
                //it's different to other bindings,
                //WHEN loading page,calculte the value
                else if(bs.startsWith("config{"))
                {
                    Config config=this.global.wfwConfig;
                    String tmps=String.valueOf(config.getValue(parseCmd(bs)));
                    if(tmps.indexOf('@') >= 0)
                    {
                        this.parseBinding(tmps,segments,last);
                        //  System.out.print("Runto");
                    }
                    else
                    {
                        last.buffer.append(tmps);
                    }
                    parseBinding(removeBindPart(bs),segments,last);
                    return;

                }

                else
                {
                    last.buffer.append(c);

                }

            }
            else
            {
                last.buffer.append(c);
            }
        }
    }

    private static String parseCmd(String s)
    {
        int st=s.indexOf("{");
        int et=s.indexOf("}",st);
        if(st < 0 || et <= st)
        {
            throw new java.lang.RuntimeException("Error parse giving Cmd:" + s);
        }
        return s.substring(st + 1,et);

    }

    private static String removeBindPart(String s)
    {
        int i=s.indexOf("}");
        if(i <= 0)
        {
            throw new java.lang.RuntimeException("Cannot find Cmd end tag:" + s);
        }

        return s.substring(i + 1);
    }

    /**
     * replace url,href,scr etc attribute by _href,_src
     *
     * apply to whole root element
     * used for page ,and component after load template
     * @param ele Element
     */
    public void resolveUrl(Element ele,LoadContext lc)
    {
        if(ele == null || ele.attributeCount() == 0)
        {
            return;
        }
        List toadd=new ArrayList(1);
        List tomove=new ArrayList(1);
        for(int i=0;i < ele.attributeCount();i++)
        {
            Attribute a=ele.attribute(i);
            if(a.getName().startsWith("_"))
            {
                String s=a.getValue().trim();
                if(!s.startsWith("/"))
                {
                    throw new java.lang.RuntimeException(
                        "Giving Url must start with /");
                }
                tomove.add(a);

                toadd.add(new org.dom4j.tree.DefaultAttribute(
                    a.getName().trim().substring(1),
                    UrlBinding.calcaultUrl(s.split("/"),lc.pagepath)
                          ));

            }

        }

        for(int i=0;i < toadd.size();i++)
        {
            ele.remove((Attribute)tomove.get(i));
            //ele.addAttribute("","");
            Attribute a=(Attribute)toadd.get(i);
            ele.addAttribute(a.getName(),a.getValue());

        }
        /*for(java.util.Iterator i=ele.elementIterator();i.hasNext();)
                 {
            Element e=(Element)i.next();
            this.resolveUrl(e);
                 }*/

    }

//           1           2     3     4
//       /pages    /companey/about/about.html
//      /otherpages/other   /aboute/somthinf.html
    /*
        private String calcaultUrl(String url)
        {
            //String s=this.loadingPage;
            String[] pages=this.loadingPage.trim().split("/");
            String[] urls=url.split("/");

            //int spc=pages.length - 1;
            int ec=0;
            for(int i=0;i < pages.length - 1;i++)
            {
                if(urls.length > i && urls[i].equals(pages[i]))
                {
                    ec++;
                }
                else
                {
                    break;
                }
            }
            int pc=pages.length - ec - 1;
            String sartp="";
            for(int i=0;i < pc;i++)
            {
                sartp+="../";
            }
            for(int i=ec;i < urls.length;i++)
            {
                sartp+=urls[i];
                if(i != urls.length - 1)
                {
                    sartp+="/";
                }
            }

            return sartp;

        }
     */
    /**
     * load locale dependent template,page,etc
     * @param path String
     * @param locale String
     * @return URL
     * @throws Exception
     */
    public java.net.URL localeResUrl(String path,String locale)
        throws Exception
    {
        if(locale != null)
        {
            int pindex=path.lastIndexOf('.');
            String tail=path.substring(pindex);
            StringBuffer sb=new StringBuffer(
                path.substring(0,pindex));
            sb.append('_').append(locale).append(tail);

            URL o=this.getResource(sb.toString());
            if(o != null)
            {
                return o;
            }
            int lindex=locale.indexOf('_');
            if(lindex > 0)
            {

                sb.setLength(pindex);
                sb.append('_').append(locale.substring(0,lindex)).append(tail);
                o=this.getResource(sb.toString());
                if(o != null)
                {
                    return o;
                }

            }
        }
        return this.getResource(path);

    }

    private String localeResStr(String path,String locale)
        throws Exception
    {
        if(locale != null)
        {
            int pindex=path.lastIndexOf('.');
            String tail=path.substring(pindex);
            StringBuffer sb=new StringBuffer(
                path.substring(0,pindex));
            sb.append('_').append(locale).append(tail);
            String s=sb.toString();
            URL o=this.getResource(s);
            if(o != null)
            {
                return s;
            }
            int lindex=locale.indexOf('_');
            if(lindex > 0)
            {

                sb.setLength(pindex);
                sb.append('_').append(locale.substring(0,lindex)).append(tail);
                s=sb.toString();
                o=this.getResource(s);
                if(o != null)
                {
                    return s;
                }

            }
        }
        return path;

    }

    /*
        private Element parseTemplate(Element ele,
                                      String template,
                                      String locale,
                                       TemplateComponent comp )
        {
            String tplPath=template;
            SAXReader reader=new SAXReader();
            Document doc=null;
            try
            {
                URL u=this.localeResUrl(tplPath,locale);

                doc=reader.read(u);
            }
            catch(Exception ex1)
            {
                throw new java.lang.RuntimeException(
                    "Error parse Template component :[" + template +
                    "]", ex1);
            }

            //  this.resolveUrl(doc.getRootElement());

            Element root=(Element)doc.selectSingleNode(
                "//*[@jwcid=\"$content$\"]");

                                                     if(root != null)
                                                     {
                    root=(Element)root.elements().get(0);

                                                     }
                                                     else
                                                     {

         root=doc.getRootElement();

                                                     }

                                                     //>>>>>>>>>>>>>>>>>build inhead string>>>>>>>>>>>>>>>>>>>>
                             List l=root.selectNodes("//*[@jwcid=\"tohead\"]");
                                 String s="";
                                                 for(int i=0;i < l.size();i++)
                     {
       Element n=(Element)l.get(i);
             n.remove(n.attribute("jwcid"));
                                     s+=n.asXML();
                                                 n.getParent().remove(n);

                     }
                                       comp.headString=s;
                                                                                              //<<<<<<<<<<<<build inhead string<<<<<<<<<<<<<<<<<<<<<
                     Element renderbody=(Element)doc.selectSingleNode(
                                                 "//*[@jwcid=\"body\"]");
                                if(renderbody != null)
                                                              {

                                      List es=ele.elements();
           for(int i=0;i < es.size();i++)
                                                                    {
        Element e=(Element)es.get(i);
        renderbody.add(e.detach());
                                                                    }

                    renderbody.remove(renderbody.attribute("jwcid"));

                                                              }
                                                                                                                                       //<<<<<<<<<<<<<<copy render body<<<<<<<<<<<<<<<<<<<<<<<<
                                                                                                                                       //>>>>>>>>>>>>>>>copy attributes>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                                      for(int i=0;i < ele.attributeCount();i++)
                                                              {
           Attribute aa=ele.attribute(i);
                                      String an=aa.getName();
              if(an.equalsIgnoreCase(JWCID) ||
                 an.equalsIgnoreCase("template"))
                                                                    {
                 continue;
                                                                    }
                                      root.add(aa.detach());
                                                              }
        ele=root;

           return ele;

                                                              }*/

       private Component parseComponent(Element ele,LoadContext lc)
       {

           String jwcid=ele.attributeValue(JWCID);
           Component comp=null;
           try
           {
               if(jwcid.trim().equalsIgnoreCase(""))
               {
                   comp=new TemplateComponent();
               }
               else if(jwcid.indexOf(".") > 0)
               {
                   Class c=loadClass(jwcid);
                   comp=(Component)c.newInstance();

               }
               else
               {

                   Class cclass=(Class)componentClasses.get(jwcid.trim().
                       toLowerCase());

                   comp=(Component)cclass.newInstance();
               }
           }
           catch(Exception ex)
           {
               throw new java.lang.RuntimeException(
                   "Error creating component:<" +
                   ele.getName() + ">" + jwcid,ex);
           }

           String a=ele.attributeValue("template");
           String locale=getLocaleFromPath(lc.pagepath);
           comp.locale=locale;
           if(a != null)
           {
               String tplPath=a;
               SAXReader reader=new SAXReader();
               reader.setEntityResolver(WfwEntityResolver.INSTANCE);
               Document doc=null;
               try
               {
                   URL u=this.localeResUrl(tplPath,locale);

                   doc=reader.read(u);
                   org.dom4j.Attribute redir=
                       doc.getRootElement().attribute(Constants.AN_Redirect);
                   while(redir != null)
                   {
                       String s=redir.getValue().trim();
                       u=this.localeResUrl(s,locale);
                       if(u == null)
                       {
                           throw new java.lang.RuntimeException(
                               "Cannot find redirect template:" + s);
                       }
                       doc=reader.read(u);
                       redir=doc.getRootElement().attribute(Constants.
                           AN_Redirect);

                   }

               }
               catch(Exception ex1)
               {
                   throw new java.lang.RuntimeException(
                       "Error loading template file for component :[" + a +
                       "]"
                       + jwcid,ex1);
               }

               //  this.resolveUrl(doc.getRootElement());

               Element root=(Element)doc.selectSingleNode(
                   "//*[@jwcid=\"$content$\"]");

               if(root != null)
               {
                   root=(Element)root.elements().get(0);

               }
               else
               {

                   root=doc.getRootElement();

               }

               //>>>>>>>>>>>>>>>>>build inhead string>>>>>>>>>>>>>>>>>>>>
               List l=root.selectNodes("//*[@jwcid=\"tohead\"]");
               String s="";
               for(int i=0;i < l.size();i++)
               {
                   Element n=(Element)l.get(i);
                   n.remove(n.attribute("jwcid"));
                   s+=n.asXML();
                   n.getParent().remove(n);

               }
               ///////////////////////

               if(!s.equals(""))
               {

//                   //List segs=new java.util.Collections.();
//                   StringBufferBag ba=new StringBufferBag();
//
//                   //only parse config binding
//                   this.parseBinding(Util.removeSpace(s),
//                                     java.util.Collections.EMPTY_LIST,ba);
//
//                   s=ba.buffer.toString();

                   LoadContext nlc=new LoadContext(lc);
                   this.parseBinding(Util.removeSpace(s),nlc);

                   comp.headSegments=nlc.getSegments();

               }

               //////////////////////////////
               //  comp.headString=s;
               //<<<<<<<<<<<<build inhead string<<<<<<<<<<<<<<<<<<<<<
               Element renderbody=(Element)doc.selectSingleNode(
                   "//*[@jwcid=\"body\"]");
               if(renderbody != null)
               {
                   /*   for(java.util.Iterator i=ele.elementIterator();i.hasNext();)
                      {
                          Element e=(Element)i.next();
                          renderbody.add(e.detach());
                      }*/

                   List es=ele.elements();
                   for(int i=0;i < es.size();i++)
                   {
                       Element e=(Element)es.get(i);
                       renderbody.add(e.detach());
                   }

                   renderbody.remove(renderbody.attribute("jwcid"));

               }
               //<<<<<<<<<<<<<<copy render body<<<<<<<<<<<<<<<<<<<<<<<<
               //>>>>>>>>>>>>>>>copy attributes>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
               for(int i=0;i < ele.attributeCount();i++)
               {
                   Attribute aa=ele.attribute(i);
                   String an=aa.getName();
                   //skip template and jwcid attribute
                   if(an.equalsIgnoreCase(JWCID) ||
                      an.equalsIgnoreCase("template"))
                   {
                       continue;
                   }
                   //over ride same name attribute using new given value

                   Attribute roota=root.attribute(an);
                   if(roota != null)
                   {
                       root.remove(roota);
                   }
                   root.add(aa.detach());
               }
               ele=root;

           }
           else
           {
               ///////////////////////////////////
               Node nnd=ele.attribute(JWCID);
               if(nnd != null)
               {
                   ele.remove(nnd); //.detach();
               }
           }
           ele=comp.parseXml(ele,lc);

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
           if(ele != null)
           {
               LoadContext nlc=new LoadContext(lc);

               List l=nlc.segments;
               StringBufferBag bag=nlc.last;

               parseElement(ele,nlc);
               if(bag.buffer.length() > 0)
               {
                   l.add(bag.getString());
               }
               comp.segments=l.toArray();
           }
           return comp;
       }

    public synchronized void setDevelopeMode(boolean developeMode)
    {
        this.developeMode=developeMode;
    }

    void setDefaulPagePath(String defaulPagePath)
    {
        this.defaulPagePath=defaulPagePath;
    }

    public String getDefaulPagePath()
    {
        return this.defaulPagePath;

    }

    void setGlobal(GlobalRef global)
    {
        this.global=global;
    }


    public static void main(String[] ss)
    {
        String path="/!sdss/!adsda/sdfdsf.html";
        if(path.charAt(1) == '!')
        {
            int i=path.indexOf('/',1);

            path=(i > 0) ? path.substring(i) : "/" + path.substring(2);

        }
        System.out.println(path);
//        PageLoader l=new PageLoader();
//        //  l.loadingPage="/a/b/c/d/e/f.html";
//        try
//        {
//            l.innerGetPage("/a/b/c/d/e/sssssssssfb.html","_CN");
//        }
//        catch(Exception ex)
//        {
//        }
//        System.out.println("[" +
//                           getLocaleFromPath(
//                               "/a/b/c/d/e/sssssssssf_zh_cb.html") +
//                           "]");
    }

    public boolean isDevelopeMode()
    {
        return developeMode;
    }

    public GlobalRef getGlobal()
    {
        return global;
    }


    private void removeIdle()
    {
        long maxage=    this.getGlobal().getWfwConfig().getInterval("cleaner-max-age","3h");
        long c=System.currentTimeMillis();
        for(Object o:this.pages.entrySet())
        {
            java.util.Map.Entry e=(java.util.Map.Entry)o;
           Page p=(Page)e.getValue();
           if(p.getPageHandler().getLastVisitTime()<c-maxage)//3 hours
           {
               this.pages.remove(e.getKey());
           }

        }
    }

}
